using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using MLSharp.Classification;
using MLSharp.Utilities;

namespace MLSharp.RProject
{
	/// <summary>
	/// The base class for R neural networks.
	/// </summary>
	/// <remarks>
	/// This class encapsulates functionality to invoke R, parse
	/// results, etc.
	/// </remarks>
	public abstract class RNeuralNetworkBase : INeuralNetClassifier
	{
		#region Protected Fields

		/// <summary>
		/// The working directory to use.
		/// </summary>
		protected string mWorkingDirectory;

		/// <summary>
		/// The path to R.exe.
		/// </summary>
		protected string mRPath;

		/// <summary>
		/// The name of the library to load.
		/// </summary>
		private readonly string mLibraryName;

		/// <summary>
		/// The command to run the network.
		/// </summary>
		private readonly string mRunCommand;

		#endregion

		#region Public Properties

		/// <summary>
		/// A string representation of the network.  This can be fed back into R to 
		/// re-create the network reference.
		/// </summary>
		public string Network { get; private set; }

		#endregion

		#region Constructors

		/// <summary>
		/// Creates a network.
		/// </summary>
		/// <param name="workingDirectory">The working directory to use.</param>
		/// <param name="rPath">The path to R.exe.</param>
		/// <param name="networkContents">The network.</param>
		/// <param name="libraryName">The name of the library to load.</param>
		/// <param name="runCommand">The command to run the network.</param>
		protected RNeuralNetworkBase(string workingDirectory, string rPath, string networkContents, string libraryName, string runCommand)
		{
			Network = networkContents;

			mWorkingDirectory = workingDirectory;

			mRPath = rPath;

			mLibraryName = libraryName;

			mRunCommand = runCommand;
		}

		#endregion

		#region Abstract Methods

		/// <summary>
		/// Verifies that the output is in the correct, expected format and
		/// performs any necessary cleaning.
		/// </summary>
		/// <param name="dataSet"></param>
		/// <param name="output"></param>
		/// <exception cref="InvalidOperationException">Thrown if there is a problem with the data.</exception>
		protected abstract void ValidateAndPrepareOutput(IDataSet dataSet, List<string> output);

		#endregion

		#region Public Methods

		/// <summary>
		/// Classifies all the instances in the specified data set.
		/// </summary>
		/// <param name="dataSet">The data set to classify.</param>
		/// <returns>The results of classifying each instance.</returns>
		public virtual ClassificationResult[] Classify(IDataSet dataSet)
		{
			//This is the file that the instances will be written to.
			string tableFilename = Path.Combine(mWorkingDirectory, NameGenerator.GetRandomName(10) + ".table");

			//And this is the script that is fed in to R.
			string rScriptFilename = Path.Combine(mWorkingDirectory, NameGenerator.GetRandomName(10) + ".R");

			//Step 1: Write the data set to a temp table file.
			RTableWriter.Write(dataSet, tableFilename);

			//Step 2: Create a new R script
			using (StreamWriter writer = File.CreateText(rScriptFilename))
			{
				//Step 3a: Import the neural network library
				writer.WriteLine("library({0})", mLibraryName);

				//Step 3b: Write the network to the R script
				writer.Write(Network);

				//Step 4: The command to load the data set from the temp file.
				writer.WriteLine("x<-as.matrix(read.table(\"{0}\", header = FALSE))", tableFilename.Replace(@"\", @"\\"));

				//Step 5: The command to run the network
				writer.WriteLine("y<-{0}", mRunCommand);

				//Step 6: The command to dump the results
				writer.WriteLine("y");
			}

			//Step 7: Run R and capture its output.
			ProcessStartInfo startInfo = new ProcessStartInfo(mRPath);
			startInfo.RedirectStandardOutput = true;
			startInfo.RedirectStandardError = true;
			startInfo.WorkingDirectory = mWorkingDirectory;
			startInfo.Arguments = string.Format("-f \"{0}\" --slave", rScriptFilename);
			//This must be false if you are redirecting input/output
			startInfo.UseShellExecute = false;
			startInfo.CreateNoWindow = true;

			List<string> output = new List<string>();

			using (Process rProcess = Process.Start(startInfo))
			{
				if (rProcess == null)
				{
					throw new NullReferenceException("rProcess is null!");
				}

				while (!rProcess.StandardOutput.EndOfStream)
				{
					output.Add(rProcess.StandardOutput.ReadLine());
				}
				rProcess.WaitForExit();
			}

			//Remove temporary files
			File.Delete(tableFilename);
			File.Delete(rScriptFilename);

			//Step 7: Parse the results
			ValidateAndPrepareOutput(dataSet, output);

			//Allocate the result array
			ClassificationResult[] results = new ClassificationResult[output.Count];

			//Loop through the output and extract the results.
			for (int i = 0; i < output.Count; i++)
			{
				//Grab an ID for this instance, if any.
				string id = dataSet.Instances[i].Label;

				double value = double.Parse(output[i].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]);

				//Find the best class for this instance.
				string classification = dataSet.Attributes[dataSet.TargetAttributeIndex].GetClosestValue(value);

				double confidence = dataSet.Attributes[dataSet.TargetAttributeIndex].GetConfidenceForClosestValue(value);

				results[i] = new ClassificationResult(classification, dataSet.Instances[i].ClassValue)
				             	{
				             		Confidence = confidence,
				             		ID = id
				             	};
			}

			return results;
		}

		#endregion
	}
}
